package racer.server;

import java.net.ServerSocket;
import java.net.InetSocketAddress;

import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;

import java.util.HashMap;
import java.util.Iterator;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
/** 
 * 
 * RaceServer class
 * uses new I/O classes with nonblocking sockets 
 *
 */
public class RaceServer {

	private static int PORT = 1234;
	
	private HashMap<SocketChannel, Player> players;
	private Selector selector;
	private ServerSocketChannel servChann;
	private ServerSocket servSock;
	
	public RaceServer() {
		players = new HashMap<SocketChannel,Player>();
		
		try {
			servChann = ServerSocketChannel.open();
			servChann.configureBlocking(false);
			
			servSock = servChann.socket();
			servSock.bind(new InetSocketAddress(PORT));
			
			selector = Selector.open();
			servChann.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			doServerStuff();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
	
	/**
	 * It is critical that the actions related to the event of a 
	 * SelectionKey, e.g. newPlayer() and recvMessage(), will not
	 * block somewhere
	 * 
	 * @throws IOException
	 */
	private void doServerStuff() throws IOException {
		while(true) {
			System.out.println("Waiting for select()");
			int numselkeys = selector.select();
			Iterator it = selector.selectedKeys().iterator();
			SelectionKey key;
			System.out.println("select() returned: " + numselkeys + " // " + selector.selectedKeys().size() + " selected keys according to selectedKeys()");
			while (it.hasNext()) {
				key = (SelectionKey)it.next();
				it.remove();
				if (key.isAcceptable())
					newPlayer(selector, key);
				else if (key.isReadable())
					recvMessage(key);
				else if (key.isWritable())
					System.out.println("key is writable");
				else if (key.isConnectable())
					System.out.println("key is connectable");
				else if(!key.isValid())
					System.out.println("invalid key");
				else
					System.out.println("Unknown key operation on : " + key);
			}
		}
	}
	
	/**
	 * Accept client, register client with selector and put client in hashmap
	 * @param selector
	 * @param key
	 */
	private void newPlayer(Selector selector, SelectionKey key) {
		System.out.println("new player");
		try {
			ServerSocketChannel servChann = (ServerSocketChannel)key.channel();
			SocketChannel sockChann = servChann.accept();
			
			sockChann.configureBlocking(false);
			sockChann.register(selector, SelectionKey.OP_READ);
			
			players.put(sockChann, new Player(sockChann,this));
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		
	}
	
	private void recvMessage(SelectionKey key) {
		System.out.println("received message");
		Player p = (Player)players.get((SocketChannel)key.channel());
		if(p == null)
			return;
		String msg = p.recvMessage();
		System.out.println(msg);
		//p.sendMessage(msg);
		sendBroadcastMessage(msg, p.getSockChann());
		
	}
	
	private void sendBroadcastMessage(String msg, SocketChannel from) {
		Iterator i = players.values().iterator();
		while(i.hasNext()) {
			Player p = (Player)i.next();
			if (p.getSockChann() != from)
				p.sendMessage(msg);
		}
	}
	
	public void removeChannel(SocketChannel sockChann) {
		players.remove(sockChann);
	}
	
	private HashMap<SocketChannel,Player> getPlayers() {
		return players;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RaceServer race = new RaceServer();
	}

}
